#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string>

#include </path/to/httplib.h>
#include </path/to/json.hpp>

using namespace std;
using json = nlohmann::json;

long long QuickInput(int mode = 0);
string StringInput(int mode = 0);
string GetData(string name, int offset, int limit, string beginDate, string endDate);
bool CmpByTime(json a, json b);
bool CmpByCourse(json a, json b);
void SearchByName(string name);
void HttpServer(string addr, int port);

class AbsenceLog {
private:
    int id;
    int begin, end, ClassNum = 0;
    string date, course = "", name, type;
public:
    AbsenceLog *pre, *next;
    int GetID() { return id;}
    string GetDate() { return date; }
    string GetName() { return name; }
    string GetCourse() { return course; }
    string GetType() { return type; }
    int GetBegin() { return begin; }
    int GetEnd() { return end; }
    json GetData() {
        json res = {
            { "id", id },
            { "date", date },
            { "ClassRange", { begin, end } },
            { "course", course },
            { "name", name },
            { "type", type },
            { "ClassNum", ClassNum }
        };
        return res;
    }
    void InputByKeyboard(AbsenceLog *_self);
    void OutputByKeyboard();
    void ModifyByKeyboard();
    void InputByAPI(AbsenceLog *_self, string _date, int _begin, int _end, string _course, string _name, string _type);
    void EditByAPI(string _date, int _begin, int _end, string _course, string _name, string _type);
};

int N;
FILE* Database;
vector<AbsenceLog> LogData;
httplib::Server svr;

int main() {
    printf("读取数据中...\n");
    Database = fopen("data.db", "r");
    while(!feof(Database) && !ferror(Database)) {
        AbsenceLog tmp;
        tmp.InputByAPI(&tmp, StringInput(1), QuickInput(1), QuickInput(1), StringInput(1), StringInput(1), StringInput(1));
        tmp.OutputByKeyboard();
    }
    fclose(Database);
    printf("数据读取....OK!  共读取 %lu 条数据\n", LogData.size());
    // LogData.pop_back();
    // AbsenceLog tmp;
    // tmp.InputByKeyboard(&tmp);
    // LogData.at(0).OutputByKeyboard();
    printf("API 启动....OK!\n");
    HttpServer("127.0.0.1", 8000);
    // LogData.at(0).ModifyByKeyboard();
    // SearchByName("张三");
    return 0;
}

void AbsenceLog::InputByKeyboard(AbsenceLog *_self) {
    printf("请输入日期(格式:YYYY-MM-DD):\n");
    date = StringInput(0);
    printf("请输入缺课节数(格式 开始-节数):\n");
    begin = QuickInput(0), end = QuickInput(0);
    ClassNum = end - begin + 1;
    printf("请输入课程名称:\n");
    course = StringInput(0);
    printf("请输入学生姓名:\n");
    name = StringInput(0);
    printf("请输入缺课类型:\n");
    type = StringInput(0);
    id = N ++;
    if (begin != -1 && end != -1) LogData.push_back(*_self);
}

void AbsenceLog::OutputByKeyboard() {
    if (begin != -1 && end != -1)
        printf("%s %d-%d %s %s %s\n", date.c_str(),
                                      begin, end,
                                      course.c_str(),
                                      name.c_str(),
                                      type.c_str()
        );
}

void AbsenceLog::ModifyByKeyboard() {
    printf("需要修改的数据类型? 0.日期 1.缺课数 2.课程名称 3.学生姓名 4.缺课类型 [0-4]: ");
    int num = QuickInput(0);
    if (num == 0) {
        printf("请输入日期(格式:YYYY-MM-DD):\n");
        date = StringInput(0);
    } else if (num == 1) {
        printf("请输入缺课数(格式 开始-节数):\n");
        begin = QuickInput(0), end = QuickInput();
        ClassNum = end - begin + 1;
    } else if (num == 2) {
        printf("请输入课程名称:\n");
        course = StringInput(0);
    } else if (num == 3) {
        printf("请输入学生姓名:\n");
        name = StringInput(0);
    } else if (num == 4) {
        printf("请输入缺课类型:\n");
        type = StringInput(0);
    } else {
        printf("非法操作!\n");
        return;
    }
    printf("修改结果:\n");
    OutputByKeyboard();
}

void AbsenceLog::InputByAPI(AbsenceLog *_self, string _date, int _begin, int _end, string _course, string _name, string _type) {
    date = _date, begin = _begin, end = _end, course = _course, name = _name, type = _type;
    ClassNum = end - begin + 1;
    if (begin != -1 && end != -1) {
        id = N ++;
        LogData.push_back(*_self);
    }
}

void AbsenceLog::EditByAPI(string _date, int _begin, int _end, string _course, string _name, string _type) {
    date = _date, begin = _begin, end = _end, course = _course, name = _name, type = _type;
    ClassNum = end - begin + 1;
}

long long QuickInput(int mode) {
    long long _y = 0;
    if (mode == 1) {
        long long _x = fgetc(Database);
        if (_x == '\n' || _x == EOF) return -1;
        while (_x < 48 || _x > 57) _x = fgetc(Database);
        while (_x >= 48 && _x <= 57) {
            _y = (_y << 3) + (_y << 1) + _x - '0';
            _x = fgetc(Database);
        }
    } else {
        long long _x = getchar();
        if (_x == '\n') return -1;
        while (_x < 48 || _x > 57) _x = getchar();
        while (_x >= 48 && _x <= 57) {
            _y = (_y << 3) + (_y << 1) + _x - '0';
            _x = getchar();
        }
    }
    return _y;
}

string StringInput(int mode) {
    string _tmp = "";
    if (mode == 1) {
        char _x = fgetc(Database);
        while (_x == '\n' && _x == ' ') _x = fgetc(Database);
        while (_x != '\n' && _x != ' ' && _x != EOF) {
            _tmp += _x;
            _x = fgetc(Database);
        }
    } else {
        char _x = getchar();
        while (_x == '\n') _x = getchar();
        while (_x != '\n') {
            _tmp += _x;
            _x = getchar();
        }
    }
    return _tmp;
}

void SearchByName(string name) {
    for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
        if (i->GetName() == name) i->OutputByKeyboard();
    }
}

string GetData(string name, int offset, int limit, string beginDate, string endDate) {
    json res;
    res["data"] = {};
    if (beginDate == "" && endDate == "") {
        if (name == "") {
            for (int i = offset; i < offset + limit && i < LogData.size(); i++)
                res["data"].push_back(LogData.at(i).GetData());
            res["total"] = LogData.size();
        } else {
            int n = 0;
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++)
                if (i->GetName() == name) {
                    res["data"].push_back(i->GetData());
                    n ++;
                }
            for (int i = 0; i < offset; i++) res["data"].erase(0);
            for (int i = limit; i < res["data"].size();) res["data"].erase(limit);
            res["total"] = n;
        }
    } else {
        if (name == "") {
            int n = 0;
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++)
                if (i->GetDate() >= beginDate && i->GetDate() <= endDate) {
                    res["data"].push_back(i->GetData());
                    n ++;
                }
            for (int i = 0; i < offset; i++) res["data"].erase(0);
            for (int i = limit; i < res["data"].size();) res["data"].erase(limit);
            res["total"] = n;
        } else {
            int n = 0;
            cout << beginDate << " " << endDate << endl;
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++)
                if (i->GetName() == name && i->GetDate() >= beginDate && i->GetDate() <= endDate) {
                    res["data"].push_back(i->GetData());
                    n ++;
                }
            for (int i = 0; i < offset; i++) res["data"].erase(0);
            for (int i = limit; i < res["data"].size();) res["data"].erase(limit);
            res["total"] = n;
        }
    }
    sort(res["data"].begin(), res["data"].end(), CmpByTime);
    return res.dump();
}

void HttpServer(string addr, int port) {
    svr.Get("/GetData", [](const httplib::Request &req, httplib::Response &res) {
        res.set_content(GetData(req.get_param_value("name"), atoi(req.get_param_value("offset").c_str()), atoi(req.get_param_value("limit").c_str()), req.get_param_value("beginDate"), req.get_param_value("endDate")), "application/json;charset=utf-8");
    });
    svr.Get("/StatisticsData", [](const httplib::Request &req, httplib::Response &res) {
        json resDate = {};
        string beginDate = req.get_param_value("beginDate"), endDate = req.get_param_value("endDate");
        if (beginDate == "" && endDate == "") {
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
                if(resDate[i->GetDate() + i->GetCourse()].is_null()) resDate[i->GetDate() + i->GetCourse()] = 1;
                else resDate[i->GetDate() + i->GetCourse()] = resDate[i->GetDate() + i->GetCourse()].get<int>() + 1;
            }
        } else {
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
                if (i->GetDate() >= beginDate && i->GetDate() <= endDate) {
                    if(resDate[i->GetDate() + i->GetCourse()].is_null()) resDate[i->GetDate() + i->GetCourse()] = 1;
                    else resDate[i->GetDate() + i->GetCourse()] = resDate[i->GetDate() + i->GetCourse()].get<int>() + 1;
                }
            }
        }
        json tmp;
        for (json::iterator i = resDate.begin(); i != resDate.end(); i++) {
            tmp.push_back({ { "date", i.key().substr(0, 10) }, { "course", i.key().substr(10) }, { "num", i.value() }});
        }
        sort(tmp.begin(), tmp.end(), CmpByCourse);
        res.set_content(tmp.dump(), "application/json;charset=utf-8");
    });
    svr.Post("/AddData", [](const httplib::Request &req, httplib::Response &res) {
        json resData = json::parse(req.body);
        string name = resData["name"].get<string>();
        string date = resData["date"].get<string>();
        int ClassRange[2] = {resData["ClassRange"][0].get<int>(), resData["ClassRange"][1].get<int>()};
        string course = resData["course"].get<string>();
        string type = resData["type"].get<string>();
        date.erase(remove(date.begin(), date.end(), ' '), date.end());
        name.erase(remove(name.begin(), name.end(), ' '), name.end());
        type.erase(remove(type.begin(), type.end(), ' '), type.end());
        course.erase(remove(course.begin(), course.end(), ' '), course.end());
        AbsenceLog tmp;
        tmp.InputByAPI(&tmp, date, ClassRange[0], ClassRange[1], course, name, type);
        printf("Add: ");
        tmp.OutputByKeyboard();
        res.set_content(tmp.GetData().dump(), "application/json;charset=utf-8");
        Database = fopen("data.db", "w");
        for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
            fprintf(Database, "%s %d-%d %s %s %s\n", i->GetDate().c_str(), i->GetBegin(), i->GetEnd(), i->GetCourse().c_str(), i->GetName().c_str(), i->GetType().c_str());
        }
        fclose(Database);
    });
    svr.Put("/EditData", [](const httplib::Request &req, httplib::Response &res) {
        json resData = json::parse(req.body);
        int id = resData["id"].get<int>();
        string name = resData["name"].get<string>();
        string date = resData["date"].get<string>();
        int ClassRange[2] = {resData["ClassRange"][0].get<int>(), resData["ClassRange"][1].get<int>()};
        string course = resData["course"].get<string>();
        string type = resData["type"].get<string>();
        date.erase(remove(date.begin(), date.end(), ' '), date.end());
        name.erase(remove(name.begin(), name.end(), ' '), name.end());
        type.erase(remove(type.begin(), type.end(), ' '), type.end());
        course.erase(remove(course.begin(), course.end(), ' '), course.end());
        if (date.c_str() && ClassRange[0] && ClassRange[1] && course.c_str() && name.c_str() && type.c_str()) {
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
                int id = resData["id"].get<int>();
                if (i->GetID() == id) {
                    i->EditByAPI(date, ClassRange[0], ClassRange[1], course, name, type);
                    printf("Edit: ");
                    i->OutputByKeyboard();
                    res.set_content(i->GetData().dump(), "application/json;charset=utf-8");
                    break;
                }
            }
        } else {
            json resData = {
                { "info", "Error" }
            };
            res.set_content(resData.dump(), "application/json;charset=utf-8");
        }
        Database = fopen("data.db", "w");
        for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
            fprintf(Database, "%s %d-%d %s %s %s\n", i->GetDate().c_str(), i->GetBegin(), i->GetEnd(), i->GetCourse().c_str(), i->GetName().c_str(), i->GetType().c_str());
        }
        fclose(Database);
    });
    svr.Delete("/DeleteData", [](const httplib::Request &req, httplib::Response &res) {
        if (req.has_param("id")){
            int id = atoi(req.get_param_value("id").c_str());
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
                if (i->GetID() == id) {
                    printf("Delete: ");
                    i->OutputByKeyboard();
                    LogData.erase(i);
                    json resData = {
                        { "info", "Success" }
                    };
                    res.set_content(resData.dump(), "application/json;charset=utf-8");
                    break;
                }
            }
            Database = fopen("data.db", "w");
            for (vector<AbsenceLog>::iterator i = LogData.begin(); i != LogData.end(); i++) {
                fprintf(Database, "%s %d-%d %s %s %s\n", i->GetDate().c_str(), i->GetBegin(), i->GetEnd(), i->GetCourse().c_str(), i->GetName().c_str(), i->GetType().c_str());
            }
            fclose(Database);
        }
    });
    svr.listen(addr.c_str(), port);
}

void Menu() {
    printf("选择操作: 0.输入记录, 1.修改记录, 2.查询学生记录, 3.统计记录 [0-3]: ");
    Menu();
}

bool CmpByTime(json a, json b) {
    if (a["date"] != b["date"]) return a["date"] < b["date"];
    if (a["ClassNum"] != b["ClassNum"])  return a["ClassNum"] > b["ClassNum"];
    if (a["name"] != b["name"]) return a["name"] < b["name"];
    return a["course"] < b["course"];
}

bool CmpByCourse(json a, json b) {
    if (a["date"] != b["date"]) return a["date"] < b["date"];
    if (a["num"] != b["num"])  return a["num"] > b["num"];
    return a["course"] < b["course"];
}